﻿using System;
using System.Collections.ObjectModel;
using System.Linq;
using RanbuOM.Common;
using RanbuOM.Serialization;

namespace RanbuOM.SSF4.BCM
{
    public class BcmFile : DataFileBase
    {
        [DataProperty]
        public MetaHeader Header { get; set; }

        [DataProperty]
        public UInt32 Unknown0x0C { get; set; }

        [DataProperty(Group = "None")]
        public ObservableCollection<BcmCharge> Charges { get; set; }

        [DataProperty(Group = "None")]
        public ObservableCollection<BcmInputSequence> InputSequences { get; set; }

        [DataProperty(Group = "None")]
        public ObservableCollection<BcmMove> Moves { get; set; }

        [DataProperty(Group = "None")]
        public ObservableCollection<BcmMoveSet> MoveSets { get; set; }

        internal override void ReadFrom(RanbuReader reader, string group)
        {
            if (group != null) { throw new ArgumentException(); }

            // Read headers.
            base.ReadFrom(reader, null);

            UInt16 cCharge = reader.ReadU16();
            UInt16 cInput = reader.ReadU16();
            UInt16 cMove = reader.ReadU16();
            UInt16 cMoveSet = reader.ReadU16();

            var pCharge = reader.ReadOffset32(this);
            var ppChargeName = reader.ReadOffset32(this);
            var pInput = reader.ReadOffset32(this);
            var ppInputName = reader.ReadOffset32(this);
            var pMove = reader.ReadOffset32(this);
            var ppMoveName = reader.ReadOffset32(this);
            var pMoveSet = reader.ReadOffset32(this);
            var ppMoveSetName = reader.ReadOffset32(this);

            reader.AssertOffset(pCharge);
            this.Charges = reader.ReadCreateObservableCollection<BcmCharge>(cCharge);
            reader.AssertOffset(ppChargeName);
            var pChargeNames = reader.ReadCreateArray<Offset32>(cCharge);

            reader.AssertOffset(pInput);
            this.InputSequences = reader.ReadCreateObservableCollection<BcmInputSequence>(cInput);
            reader.AssertOffset(ppInputName);
            var pInputNames = reader.ReadCreateArray<Offset32>(cInput);

            reader.AssertOffset(pMove);
            this.Moves = reader.ReadCreateObservableCollection<BcmMove>(cMove);
            reader.AssertOffset(ppMoveName);
            var pMoveNames = reader.ReadCreateArray<Offset32>(cMove);

            reader.AssertOffset(pMoveSet);
            var moveSetHeaders = reader.ReadCreateArray<BcmMoveSetHeader>(cMoveSet);
            this.MoveSets = reader.ReadCreateObservableCollection<BcmMoveSet>(cMoveSet);
            reader.AssertOffset(ppMoveSetName);
            var pMoveSetNames = reader.ReadCreateArray<Offset32>(cMoveSet);

            for (int i = 0; i < cMoveSet; i++)
            {
                reader.AssertOffset(moveSetHeaders[i].Offset);
                this.MoveSets[i].Moves = reader.ReadCreateObservableCollection<IndexReference<Int16, BcmMove>>((int)moveSetHeaders[i].Count);
            }

            reader.ReadListItems(this.Charges, "Name", pChargeNames, true);
            reader.ReadListItems(this.InputSequences, "Name", pInputNames, true);
            reader.ReadListItems(this.Moves, "Name", pMoveNames, true);
            reader.ReadListItems(this.MoveSets, "Name", pMoveSetNames, true);
        }

        internal override void WriteTo(RanbuWriter writer, string group)
        {
            if (group != null) { throw new ArgumentException(); }

            // Write headers.
            base.WriteTo(writer, null);

            writer.WriteU16((ushort)this.Charges.Count);
            writer.WriteU16((ushort)this.InputSequences.Count);
            writer.WriteU16((ushort)this.Moves.Count);
            writer.WriteU16((ushort)this.MoveSets.Count);

            var pCharge = writer.WriteOffsetPlaceholder(this);
            var ppChargeName = writer.WriteOffsetPlaceholder(this);
            var pInput = writer.WriteOffsetPlaceholder(this);
            var ppInputName = writer.WriteOffsetPlaceholder(this);
            var pMove = writer.WriteOffsetPlaceholder(this);
            var ppMoveName = writer.WriteOffsetPlaceholder(this);
            var pMoveSet = writer.WriteOffsetPlaceholder(this);
            var ppMoveSetName = writer.WriteOffsetPlaceholder(this);

            // We assume the only data that can be absent is Charge.
            if (this.Charges.Count > 0)
            {
                writer.FillOffsetValue(pCharge);
                writer.WriteList(this.Charges);
                writer.FillOffsetValue(ppChargeName);
            }
            var pChargeNames = writer.WriteOffsetPlaceholderArray(this, this.Charges.Count);

            writer.FillOffsetValue(pInput);
            writer.WriteList(this.InputSequences);
            writer.FillOffsetValue(ppInputName);
            var pInputNames = writer.WriteOffsetPlaceholderArray(this, this.InputSequences.Count);
            
            writer.FillOffsetValue(pMove);
            writer.WriteList(this.Moves);
            writer.FillOffsetValue(ppMoveName);
            var pMoveNames = writer.WriteOffsetPlaceholderArray(this, this.Moves.Count);

            writer.FillOffsetValue(pMoveSet);
            var moveSetHeaderQuery = from m in this.MoveSets
                                     select new BcmMoveSetHeader { Count = (uint)m.Moves.Count, Offset = new Offset32() };
            var moveSetHeaders = moveSetHeaderQuery.ToArray();
            writer.WriteList(moveSetHeaders);
            writer.FillOffsetValue(ppMoveSetName);
            var pMoveSetNames = writer.WriteOffsetPlaceholderArray(this, this.MoveSets.Count);

            for (int i = 0; i < this.MoveSets.Count; i++)
            {
                writer.FillOffsetValue(moveSetHeaders[i].Offset);
                writer.WriteList(this.MoveSets[i].Moves);
            }

            writer.WriteList(this.Charges, "Name", pChargeNames);
            writer.WriteList(this.InputSequences, "Name", pInputNames);
            writer.WriteList(this.Moves, "Name", pMoveNames);
            writer.WriteList(this.MoveSets, "Name", pMoveSetNames);
        }
    }
}
